#ifndef QLIB_HEADER_CURVE
#define QLIB_HEADER_CURVE

#include "qlib/core/array.h"
#include "qlib/core/vec2.h"
#include "qlib/utils/utils.h"

#include <algorithm>
#include <cfloat>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <vector>

namespace qlib {
enum struct EndType {
  Flush = 0,
  Round,
  HalfWidth,
  Extended,  // Use end_extensions
  Smooth,    // Becomes Round if simple_path
  Function,  // Use end_function(...)
};

enum struct JoinType {
  Natural = 0,  // Only bevel acute joins
  Miter,
  Bevel,
  Round,
  Smooth,    // Becomes Round if simple_path
  Function,  // Use join_function(...)
};

enum struct BendType {
  None = 0,
  Circular,  // Use bend_radius
  Function,  // Use bend_function(...)
};

typedef Array<Vec2d> (*JoinFunction)(const Vec2d, const Vec2d, const Vec2d, const Vec2d, const Vec2d, double, void*);

typedef Array<Vec2d> (*EndFunction)(const Vec2d, const Vec2d, const Vec2d, const Vec2d, void*);

typedef Array<Vec2d> (*BendFunction)(double, double, double, const Vec2d, void*);

struct QLIB_API LineElement {
  LineElement();

  Vec2dArray half_width_and_offset;

  JoinType join_type;
  JoinFunction join_function;
  void* join_function_data;  // User data passed directly to join_function

  EndType end_type;
  Vec2d end_extensions;
  EndFunction end_function;
  void* end_function_data;  // User data passed directly to end_function

  BendType bend_type;
  double bend_radius;
  BendFunction bend_function;
  void* bend_function_data;  // User data passed directly to bend_function
};

struct PXXData {
  PXXData();

  uint8_t calc_only;
  uint8_t dwelltime_selection;
  uint16_t unused;
  double pitch_parallel_to_path;
  double pitch_perpendicular_to_path;
  double pitch_scale;
  int32_t periods;
  int32_t grating_type;
  int32_t dots_per_cycle;
  int32_t ret_base_pixel_count;
  int32_t ret_pixel_count;
  double ret_stage_speed;
  double ret_dwell_time;
  uint8_t free[190];
  uint16_t revision;

  void little_endian_swap();
};

struct RaithData {
  RaithData();

  double pitch_parallel_to_path;
  double pitch_perpendicular_to_path;
  double pitch_scale;
  int32_t periods;
  int32_t grating_type;
  int32_t dots_per_cycle;
  uint8_t dwelltime_selection;
  std::string base_cell_name;

  PXXData to_pxxdata() const;
  void from_pxxdata(PXXData const& pxxdata);
};

typedef Vec2d (*ParametricVec2)(double, void*);

struct CurveInstruction {
  CurveInstruction();

  union {
    char command;
    double number;
  };
};

/// @brief 曲线
class QLIB_API Curve {
 public:
  Curve();
  Curve(const Curve& curve) = default;

  void init(const Vec2d initial_position, double tolerance_);

  void append(const Vec2d v);
  void append_unsafe(const Vec2d v);
  void remove(uint64_t index);
  int ensure_slots(uint64_t free_slots);
  bool closed() const;

  void horizontal(double coord_x, bool relative);
  void horizontal(const Array<double> coord_x, bool relative);
  void vertical(double coord_y, bool relative);
  void vertical(const Array<double> coord_y, bool relative);
  void segment(Vec2d end_point, bool relative);
  void segment(const Array<Vec2d> _points, bool relative);

  void cubic(const Array<Vec2d> _points, bool relative);
  void cubic_smooth(const Array<Vec2d> _points, bool relative);

  void quadratic(const Array<Vec2d> _points, bool relative);
  void quadratic_smooth(Vec2d end_point, bool relative);
  void quadratic_smooth(const Array<Vec2d> _points, bool relative);

  void bezier(const Array<Vec2d> _points, bool relative);
  void interpolation(const Array<Vec2d> _points, double* angles, bool* angle_constraints, Vec2d* tension, double initial_curl, double final_curl,
                     bool cycle, bool relative);
  void arc(double radius_x, double radius_y, double initial_angle, double final_angle, double rotation);
  void parametric(ParametricVec2 curve_function, void* data, bool relative);
  int64_t commands(const CurveInstruction* items, uint64_t count);
  void turn(double radius, double angle);

 private:
  void append_cubic(const Vec2d p0, const Vec2d p1, const Vec2d p2, const Vec2d p3);
  void append_quad(const Vec2d p0, const Vec2d p1, const Vec2d p2);
  void append_bezier(const Array<Vec2d> ctrl);

 public:
  Array<Vec2d> points;

  double tolerance;

  Vec2d last_ctrl;

  void* owner;
};
}  // namespace qlib

#endif